/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.utils;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.tianyun.cloud.common.enums.YesOrNoEnum;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 树形结构数据构造器
 *
 * <p>
 * 示例代码： <code>
 * <p>
 * List<Node<Integer>> original = new ArrayList<>();
 * original.add(new Node<Integer>(1, 0, "节点1", 0));
 * original.add(new Node<Integer>(2, 0, "节点2", 0));
 * original.add(new Node<Integer>(3, 0, "节点3", 0));
 * original.add(new Node<Integer>(4, 1, "节点1.1", 0));
 * original.add(new Node<Integer>(5, 1, "节点1.2", 0));
 * original.add(new Node<Integer>(6, 4, "节点1.1.1", 0));
 * original.add(new Node<Integer>(7, 5, "节点1.2.1", 0));
 * original.add(new Node<Integer>(8, 2, "节点2.1", 0));
 * <p>
 * BiPredicate<Node<Integer>, String> rule = (o, k) -> o.getName().contains(k);
 * rule = rule.or((o, k) -> o.getId().toString().equals(k));
 * <p>
 * TreeBuilder<Node<Integer>, Integer> builder = TreeBuilder.tree();
 * <p>
 * Node<Integer> root = builder.nodeId(Node::getId)
 * .parentId(Node::getPid)
 * .sort(Node::getSort)
 * .childrens(Node::getChildrens, Node::setChildrens)
 * //.expand(Node::getExpand, Node::setExpand, TreeNodeStrategy.QUERY_MATCHER_EXPAND)
 * .query("节点1", rule)
 * .buildAndSet(new Node<Integer>(0, -1, "根节点", 0), original);
 *
 * </code>
 *
 * @auther ebert_chan
 */
public class TreeBuilder<E, T> {

    private Function<E, T> nodeIdGetter;

    private Function<E, T> parentIdGetter;

    private Function<E, List<E>> childrensGetter;

    private BiConsumer<E, List<E>> childrensSetter;

    private Function<E, Integer> sortGetter;

    private Comparator<E> comparator;

    private String keywords;

    private BiPredicate<E, String> rule;

    private BiConsumer<E, Integer> expandSetter;

    private Function<E, Integer> expandGetter;

    private TreeNodeStrategy strategy;

    private TreeBuilder() {
    }

    /**
     * 创建构造器
     *
     * @return
     * @auther ebert_chan
     */
    public static <E, T> TreeBuilder<E, T> tree() {
        return new TreeBuilder<E, T>();
    }

    /**
     * 设置nodeId
     *
     * @param nodeIdGetter
     * @return
     * @auther ebert_chan
     */
    public TreeBuilder<E, T> nodeId(Function<E, T> nodeIdGetter) {
        this.nodeIdGetter = nodeIdGetter;
        return this;
    }

    /**
     * 设置parentId
     *
     * @param parentIdGetter
     * @return
     * @auther ebert_chan
     */
    public TreeBuilder<E, T> parentId(Function<E, T> parentIdGetter) {
        this.parentIdGetter = parentIdGetter;
        return this;
    }

    /**
     * 设置childrens
     *
     * @param childrensGetter
     * @param childrensSetter
     * @return
     * @auther ebert_chan
     */
    public TreeBuilder<E, T> childrens(Function<E, List<E>> childrensGetter, BiConsumer<E, List<E>> childrensSetter) {
        this.childrensGetter = childrensGetter;
        this.childrensSetter = childrensSetter;
        return this;
    }

    /**
     * 设置sort
     *
     * @param sortGetter
     * @return
     * @auther ebert_chan
     */
    public TreeBuilder<E, T> sort(Function<E, Integer> sortGetter) {
        this.sortGetter = sortGetter;
        return this;
    }

    /**
     * 设置expand
     *
     * @param expandGetter
     * @param expandSetter
     * @param strategy
     * @return
     * @auther ebert_chan
     */
    public TreeBuilder<E, T> expand(Function<E, Integer> expandGetter, BiConsumer<E, Integer> expandSetter, TreeNodeStrategy strategy) {
        Assert.notNull(expandGetter, "the expandGetter function is not null");
        Assert.notNull(expandSetter, "the expandSetter function is not null");

        this.expandSetter = expandSetter;
        this.expandGetter = expandGetter;
        this.strategy = strategy != null ? strategy : TreeNodeStrategy.ALWAYS_COLLAPSE;
        return this;
    }

    /**
     * 自定义排序
     *
     * @param comparator
     * @return
     * @auther ebert_chan
     */
    public TreeBuilder<E, T> comparator(Comparator<E> comparator) {
        this.comparator = comparator;
        return this;
    }

    /**
     * 设置keywords匹配规则
     *
     * @param keywords
     * @param rule
     * @return
     * @auther ebert_chan
     */
    public TreeBuilder<E, T> query(String keywords, BiPredicate<E, String> rule) {
        this.keywords = keywords != null ? keywords.trim() : keywords;

        if (StringUtils.isNotEmpty(this.keywords)) {
            Assert.notNull(rule, "matching rules is not null");
        }

        this.rule = rule;
        return this;
    }

    /**
     * 构建树
     *
     * @param parentId 父级ID
     * @param original 原始数据
     * @return
     * @auther ebert_chan
     */
    public List<E> build(T parentId, List<E> original) {

        Assert.notNull(parentId, "the parent node id value is not null");
        Assert.notNull(original, "the original list is not null");

        this.validate();

        List<E> match = searchTreeNode(original);

        List<E> root = findRootNode(parentId, match);

        if (!CollectionUtils.isEmpty(root)) {
            for (E node : root) {
                list2Tree(node, match);
            }
            if (this.sortGetter != null || this.comparator != null) {
                root.sort(this.comparator());
            }
        }

        return root;
    }

    /**
     * 构建树，设置树的子节点，返回树的根节点
     *
     * @param parentNode 父节点
     * @param original   原始数据
     * @return
     * @auther ebert_chan
     */
    public E buildAndSet(E parentNode, List<E> original) {
        Assert.notNull(parentNode, "the parent node value is not null");

        List<E> childrens = this.build(nodeIdGetter.apply(parentNode), original);
        childrensSetter.accept(parentNode, childrens);

        this.injectExpandValue(parentNode);

        return parentNode;
    }

    private void validate() {
        Assert.notNull(nodeIdGetter, "the nodeIdGetter function is not null");
        Assert.notNull(parentIdGetter, "the parentIdGetter function is not null");
        Assert.notNull(childrensGetter, "the childrensGetter function is not null");
        Assert.notNull(childrensSetter, "the childrensSetter function is not null");
        // Assert.isTrue((comparator != null || sortGetter != null), "the comparator function is not null");
    }

    /**
     * 组装树形结构
     * <p>
     *
     * @param parentNode
     * @param original
     * @author ebert_chan
     */
    private void list2Tree(E parentNode, List<E> original) {
        List<E> childrens = this.childrensGetter.apply(parentNode);
        Iterator<E> it = original.iterator();
        while (it.hasNext()) {
            E node = it.next();
            if (nodeIdGetter.apply(parentNode).equals(parentIdGetter.apply(node))) {
                if (childrens == null) {
                    childrens = new ArrayList<>();
                    childrensSetter.accept(parentNode, childrens);
                }
                childrens.add((E) node);
                it.remove();
            }
        }

        if (CollectionUtils.isEmpty(childrens)) {
            return;
        }

        if (this.sortGetter != null || this.comparator != null) {
            childrens.sort(this.comparator());
        }

        for (E node : childrens) {
            list2Tree(node, original);
        }

        this.injectExpandValue(parentNode);

    }

    /**
     * 注入expand值
     *
     * @param parentNode
     * @auther ebert_chan
     */
    private void injectExpandValue(E parentNode) {
        if (strategy == null || expandSetter == null || expandGetter == null) {
            return;
        }

        List<E> childrens = childrensGetter.apply(parentNode);
        if (CollectionUtils.isEmpty(childrens)) {
            return;
        }

        switch (strategy) {
            case ALWAYS_EXPAND:
                expandSetter.accept(parentNode, YesOrNoEnum.YES.getValue());
                break;
            case ALWAYS_COLLAPSE:
                expandSetter.accept(parentNode, YesOrNoEnum.NO.getValue());
                break;
            case QUERY_MATCHER_EXPAND:
                if (StringUtils.isEmpty(keywords) || rule == null) {
                    expandSetter.accept(parentNode, YesOrNoEnum.YES.getValue());
                } else {
                    boolean isFind = Boolean.FALSE;
                    for (E node : childrens) {
                        isFind = YesOrNoEnum.YES.getValue().equals(expandGetter.apply(node)) || rule.test(node, keywords);
                        if (isFind) {
                            break;
                        }
                    }
                    if (isFind) {
                        expandSetter.accept(parentNode, YesOrNoEnum.YES.getValue());
                    } else {
                        expandSetter.accept(parentNode, expandGetter.andThen(v -> v != null ? v : YesOrNoEnum.NO.getValue()).apply(parentNode));
                    }
                }
                break;
        }
    }

    /**
     * 查找根节点
     * <p>
     *
     * @param parentId
     * @param original
     * @return
     * @author ebert_chan
     */
    private List<E> findRootNode(T parentId, List<E> original) {
        if (CollectionUtils.isEmpty(original)) {
            return null;
        }

        List<E> root = new ArrayList<>();
        for (E node : original) {
            if (this.parentIdGetter.apply(node).equals(parentId)) {
                root.add(node);
            }
        }
        return root;
    }

    /**
     * 通过关键字搜索关联节点，匹配节点的所有子节点和父节点
     * <p>
     *
     * @param original
     * @param keyword
     * @return
     * @author ebert_chan
     */
    private List<E> searchTreeNode(List<E> original) {
        if (StringUtils.isEmpty(keywords) || rule == null) {
            return original;
        }

        Map<E, E> match = original.stream().filter(o -> {
            return rule.test(o, keywords);
        }).collect(Collectors.toMap(Function.identity(), Function.identity(), (o1, o2) -> o1));

        if (match.size() <= 0) {
            // 返回空结果集
            return Collections.emptyList();
        }

        if (match.size() == original.size()) {
            // 全匹配无需处理，直接返回
            return original;
        }

        List<E> keys = new ArrayList<>(match.keySet());
        for (E e : keys) {
            searchChildrenNode(e, original, match);
        }
        for (E e : keys) {
            searchParentNode(e, original, match);
        }
        return match.keySet().stream().collect(Collectors.toList());
    }

    /**
     * 搜索节点的所有子节点
     * <p>
     *
     * @param parentNode
     * @param original
     * @param match
     * @author ebert_chan
     */
    private void searchChildrenNode(E parentNode, List<E> original, Map<E, E> match) {
        Iterator<E> it = original.iterator();
        List<E> childrens = new ArrayList<>();
        while (it.hasNext()) {
            E e = it.next();
            if (nodeIdGetter.apply(parentNode).equals(parentIdGetter.apply(e))) {
                boolean notContains = !match.containsKey(e);
                if (notContains) {
                    match.put(e, e);
                    childrens.add(e);
                }
                it.remove();
            }
        }
        for (E e : childrens) {
            searchChildrenNode(e, original, match);
        }
    }

    /**
     * 搜索父节点
     * <p>
     *
     * @param node
     * @param original
     * @param match
     * @author ebert_chan
     */
    private void searchParentNode(E node, List<E> original, Map<E, E> match) {
        Iterator<E> it = original.iterator();
        while (it.hasNext()) {
            E e = it.next();
            if (nodeIdGetter.apply(e).equals(parentIdGetter.apply(node))) {
                boolean notContains = !match.containsKey(e);
                if (notContains) {
                    match.put(e, e);
                }
                it.remove();
                if (notContains) {
                    searchParentNode(e, original, match);
                }
                break;
            }
        }
    }

    /**
     * 比较器
     *
     * @return
     * @auther ebert_chan
     */
    private Comparator<E> comparator() {
        return Optional.ofNullable(this.comparator).orElse((o1, o2) -> {
            Integer s1 = sortGetter.apply(o1);
            Integer s2 = sortGetter.apply(o2);

            if (s1 == null && s2 == null) {
                return 0;
            }
            if (s1 == null && s2 != null) {
                return 1;
            }
            if (s1 != null && s2 == null) {
                return -1;
            }
            int v = s1.compareTo(s2);
            if (v != 0) {
                return v;
            }

            v = nodeIdGetter.apply(o1).toString().compareTo(nodeIdGetter.apply(o2).toString());

            return v > 0 ? 1 : v < 0 ? -1 : 0;
        });
    }

    /**
     * 树节点显示策略
     *
     * @auther ebert_chan
     */
    public static enum TreeNodeStrategy {
        /**
         * 全部展开
         */
        ALWAYS_EXPAND,
        /**
         * 全部收起
         */
        ALWAYS_COLLAPSE,
        /**
         * 搜索匹配项展开，若未设置rule和keywords，处理策略同ALWAYS_EXPAND
         */
        QUERY_MATCHER_EXPAND;
    }

}
